﻿using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using vJine.Core.IoC;

namespace vJine.Core.ORM {
    public partial class DataManager {

        static DataManager() {
            Tthis = typeof(DataManager);
            DataManager.Init_drMethods();
        }

        static readonly Type Tthis = null;
        /// <summary>
        /// 生成插入代理
        /// </summary>
        /// <typeparam name="Tadapter">适配器类型</typeparam>
        /// <typeparam name="Tentity">实体类型</typeparam>
        /// <param name="Map">属性映射</param>
        /// <param name="isDebug">是否为Debug</param>
        /// <param name="container">代理所属类</param>
        /// <returns>插入代理</returns>
        public static Exec<Tentity, DbCommand> I<Tadapter, Tentity>(IList<Class<Tentity>.Property> Map, bool isDebug = false, Type container = null) where Tadapter : IDbAdapter<Tadapter> {
            if(Map == null || Map.Count == 0) {
                Map = OrmConfig<Tadapter, Tentity>.Fields;
            }
            if(container == null) {
                container = DataManager.Tthis;
            }

            DynamicMethod dmCoppier =
                new DynamicMethod("", Reflect.@void, new Type[] { typeof(Tentity), T_DbCommand }, container, true);
            ILGenerator ilCpGen = dmCoppier.GetILGenerator();
            if(isDebug) {
                ilCpGen.EmitWriteLine("=======================+++++++=======================");
            }

            LocalBuilder lvParams = ilCpGen.DeclareLocal(typeof(DbParameterCollection));
            {
                ilCpGen.Emit(OpCodes.Nop);
                ilCpGen.Emit(OpCodes.Ldarg_1);
                ilCpGen.Emit(OpCodes.Callvirt, DataManager.migetParameters);
                ilCpGen.Emit(OpCodes.Stloc, lvParams);
            }

            for(int i = 0, len = Map.Count; i < len; i++) {
                Class<Tentity>.Property p_i = Map[i];
                if(isDebug) {
                    ilCpGen.EmitWriteLine(p_i.Name + Environment.NewLine);
                }

                Label lbIfDbNullIgnore = ilCpGen.DefineLabel();
                Label lbSetParamValue = ilCpGen.DefineLabel();

                ilCpGen.Emit(OpCodes.Ldloc, lvParams);
                Emit.Opc_Ldc(ilCpGen, i);
                ilCpGen.Emit(OpCodes.Callvirt, DataManager.migetItem); //*比较getItem与数组的速度

                MapAttribute map = OrmConfig<Tadapter, Tentity>.GetMap(p_i);
                bool IsBox = true;
                if(map.Conv_I != null) {
                    Type p_type = p_i.IsNullable ? p_i.pTypeNull : p_i.pType;
                    if(p_i.IsEnum) {
                        IsBox = !(Enum.GetUnderlyingType(p_type) == map.IparamType || p_type == map.IparamType);
                    } else {
                        IsBox = !(p_type == map.IparamType);
                    }
                }

                ilCpGen.Emit(OpCodes.Ldarg_0);

                Emit.Opc_Get(p_i.IsNullable, lbIfDbNullIgnore, p_i.pType, p_i.get, ilCpGen, true, IsBox, map.DbType);
                
                MethodInfo mConv_I = map.Conv_I;
                if(mConv_I != null) {
                    if(map.I_HasPropertyName) {
                        ilCpGen.Emit(OpCodes.Ldstr, p_i.Name);
                    }
                    Emit.Opc_Call(ilCpGen, mConv_I);
                }

                if(p_i.IsNullable) {
                    ilCpGen.Emit(OpCodes.Br, lbSetParamValue);
                    {
                        ilCpGen.MarkLabel(lbIfDbNullIgnore);
                        ilCpGen.Emit(OpCodes.Ldsfld, DataManager.fldDbNullValue);
                    }
                }
                ilCpGen.MarkLabel(lbSetParamValue);
                ilCpGen.Emit(OpCodes.Callvirt, DataManager.misetParamValue); //*
            }

            ilCpGen.Emit(OpCodes.Ret);
            return
                dmCoppier.CreateDelegate(typeof(Exec<Tentity, DbCommand>)) as Exec<Tentity, DbCommand>;
        }
        /// <summary>
        /// 生成查询代理
        /// </summary>
        /// <typeparam name="Tadapter">适配器类型</typeparam>
        /// <typeparam name="Tentity">实体类型</typeparam>
        /// <param name="Map">类型映射</param>
        /// <param name="isDebug">是否Debug</param>
        /// <param name="container">代理所属类</param>
        /// <returns>查询代理</returns>
        public static Exec<DbDataReader, Tentity> Q<Tadapter, Tentity>(IList<Class<Tentity>.Property> Map, bool isDebug = false, Type container = null) where Tadapter : IDbAdapter<Tadapter> {
            if(Map == null || Map.Count == 0) {
                Map = OrmConfig<Tadapter, Tentity>.Fields;
            }
            if(container == null) {
                container = DataManager.Tthis;
            }

            DynamicMethod dbCoppier =
                new DynamicMethod("", Reflect.@void, new Type[] { T_DbDataReader, typeof(Tentity) }, container, true);
            ILGenerator ilCpGen = dbCoppier.GetILGenerator();

            for(int i = 0, len = Map.Count; i < len; i++) {
                Class<Tentity>.Property p_i = Map[i];
                if(isDebug)
                {//For Debug
                    LocalBuilder lv_prop_name = ilCpGen.DeclareLocal(Reflect.@string);
                    ilCpGen.Emit(OpCodes.Ldstr, p_i.Name);
                    ilCpGen.Emit(OpCodes.Stloc, lv_prop_name);
                    ilCpGen.EmitWriteLine(lv_prop_name);
                }

                bool IsNullable = p_i.IsNullable;
                Type pType = IsNullable ? (p_i.pTypeNull ?? p_i.pType) : p_i.pType; //string IsNullable

                bool IsIgnorable = IsNullable || !pType.IsValueType;
                Label lbIfIsNullIgnore = default(Label);
                if(IsIgnorable) {
                    lbIfIsNullIgnore = ilCpGen.DefineLabel();
                    {//If dbReader Value is null goto Ignore
                        ilCpGen.Emit(OpCodes.Ldarg_0);
                        Emit.Opc_Ldc(ilCpGen, i);
                        //ilCpGen.Emit(OpCodes.Callvirt, miDrIsDbNull);
                        Emit.Opc_Call(ilCpGen, miDrIsDbNull);
                        ilCpGen.Emit(OpCodes.Brtrue_S, lbIfIsNullIgnore);
                    }//Else
                }
                {
                    ilCpGen.Emit(OpCodes.Ldarg_1);//Load Entity
                    {//dbReader.GetValue(#);
                        ilCpGen.Emit(OpCodes.Ldarg_0); //Load dbReader
                        Emit.Opc_Ldc(ilCpGen, i);

                        MapAttribute map = OrmConfig<Tadapter, Tentity>.GetMap(p_i);
                        MethodInfo mConv_Q = map.Conv_Q;

                        MethodInfo m_getValue = null;
                        if(mConv_Q != null) {
                            m_getValue = getDrMethod(map.QparamType);
                            if(mConv_Q.ReturnType != pType && (p_i.IsEnum && mConv_Q.ReturnType != Enum.GetUnderlyingType(pType))) {
                                throw new CoreException("属性[{0}]，方法[{1}]，返回类型冲突", p_i.Name, m_getValue.Name);
                            }

                            Emit.Opc_Call(ilCpGen, m_getValue);
                            if(map.Q_HasPropertyName) {
                                ilCpGen.Emit(OpCodes.Ldstr, p_i.Name);
                            }
                            Emit.Opc_Call(ilCpGen, mConv_Q);
                        } else {
                            if(p_i.IsEnum) {
                                pType = Enum.GetUnderlyingType(pType);
                            }

                            m_getValue = getDrMethod(pType);

                            if(m_getValue.ReturnType != pType) {
                                throw new CoreException("属性[{0}]，方法[{1}], 返回类型冲突", p_i.Name, m_getValue.Name);
                            }
                            Emit.Opc_Call(ilCpGen, m_getValue);
                        }

                        if(pType == Reflect.@string && map.TrimString) {
                            Emit.Opc_Call(ilCpGen, stringTrim);
                        }

                        if(IsNullable) {
                            ilCpGen.Emit(OpCodes.Newobj, p_i.ctorNull);
                        }
                    }
                    Emit.Opc_Call(ilCpGen, p_i.set); //Set Entity Value
                }
                if(IsIgnorable) {
                    ilCpGen.MarkLabel(lbIfIsNullIgnore);
                }
            }

            ilCpGen.Emit(OpCodes.Ret);
            return
                dbCoppier.CreateDelegate(typeof(Exec<DbDataReader, Tentity>)) as Exec<DbDataReader, Tentity>;
        }

        #region Init
        static Type get_compatible_type(Type type) {
            if(type == typeof(sbyte)) {
                return typeof(byte);
            } else if(type == typeof(ushort)) {
                return typeof(short);
            } else if(type == typeof(uint)) {
                return typeof(int);
            } else if(type == typeof(ulong)) {
                return typeof(long);
            } else if(type.IsEnum) {
                return Enum.GetUnderlyingType(type);
            } else {
                return type;
            }
        }

        static MethodInfo getDrMethod(Type type) {
            //type = get_compatible_type(type);
            return drMethods[type];
        }

        private static Dictionary<Type, MethodInfo> drMethods = new Dictionary<Type, MethodInfo>();
        static void Init_drMethods() {
            Type[] drmArguments = new Type[] { Reflect.@int };

            MethodInfo getBool =
                T_DbDataReader.GetMethod("GetBoolean", drmArguments);
            MethodInfo getByte =
                T_DbDataReader.GetMethod("GetByte", drmArguments);
            MethodInfo getInt16 =
                T_DbDataReader.GetMethod("GetInt16", drmArguments);
            MethodInfo getInt32 =
                T_DbDataReader.GetMethod("GetInt32", drmArguments);
            MethodInfo getInt64 =
                T_DbDataReader.GetMethod("GetInt64", drmArguments);
            MethodInfo getDouble =
                T_DbDataReader.GetMethod("GetDouble", drmArguments);
            MethodInfo getFloat =
                T_DbDataReader.GetMethod("GetFloat", drmArguments);
            MethodInfo getDateTime =
                T_DbDataReader.GetMethod("GetDateTime", drmArguments);
            MethodInfo getDecimal =
                T_DbDataReader.GetMethod("GetDecimal", drmArguments);

            MethodInfo getChar =
                T_DbDataReader.GetMethod("GetChar", drmArguments);
            MethodInfo getString =
                T_DbDataReader.GetMethod("GetString", drmArguments);
            MethodInfo getGuid =
                T_DbDataReader.GetMethod("GetGuid", drmArguments);

            MethodInfo getValue =
                T_DbDataReader.GetMethod("GetValue", drmArguments);

            drMethods.Add(Reflect.@bool, getBool);
            drMethods.Add(Reflect.@sbyte, getValue);
            drMethods.Add(Reflect.@byte, getByte);
            drMethods.Add(Reflect.@short, getInt16);
            drMethods.Add(Reflect.@ushort, getValue);
            drMethods.Add(Reflect.@int, getInt32);
            drMethods.Add(Reflect.@uint, getValue);
            drMethods.Add(Reflect.@long, getInt64);
            drMethods.Add(Reflect.@ulong, getValue);
            drMethods.Add(Reflect.@double, getDouble);
            drMethods.Add(Reflect.@float, getFloat);

            drMethods.Add(Reflect.@decimal, getDecimal);

            drMethods.Add(Reflect.@DateTime, getDateTime);

            drMethods.Add(Reflect.@char, getChar);
            drMethods.Add(Reflect.@string, getString);
            drMethods.Add(Reflect.@guid, getGuid);
            drMethods.Add(Reflect.@byteArray, getValue);

            drMethods.Add(Reflect.@object, getValue);
        }

        static readonly Type T_DbCommand = typeof(DbCommand);
        static readonly Type T_DbDataReader = typeof(DbDataReader);

        private static MethodInfo miDrIsDbNull =
            typeof(DbDataReader).GetMethod("IsDBNull", new Type[] { Reflect.@int });
        static readonly MethodInfo migetParameters =
            Reflect.GetProperty<DbCommand>("Parameters").GetGetMethod();
        static readonly MethodInfo migetItem =
            typeof(DbParameterCollection).GetProperty("Item", new Type[] { typeof(int) }).GetGetMethod();
        static readonly MethodInfo misetParamValue =
            typeof(DbParameter).GetProperty("Value").GetSetMethod();
        static readonly MethodInfo stringTrim =
            Reflect.@string.GetMethod("Trim", Type.EmptyTypes);
        static readonly FieldInfo fldDbNullValue =
            typeof(DBNull).GetField("Value", BindingFlags.Public | BindingFlags.Static);
        #endregion
    }
}
